package code.springboot.idempotent.aspect;

import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.spring.SpringUtil;
import code.springboot.idempotent.annotation.Idempotent;
import code.springboot.idempotent.constant.IdempotentConstant;
import code.springboot.idempotent.keyresolver.KeyResolver;
import code.springboot.idempotent.redis.IdempotentRedisService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.util.Assert;


import java.util.HashMap;
import java.util.Map;

@Aspect
@RequiredArgsConstructor
@Slf4j
public class IdempotentAspect {

    /**
     * redisTemplate封装
     */
    private final IdempotentRedisService idempotentRedisService;

    /**
     * 缓存幂等注解信息
     */
    private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL
            = ThreadLocal.withInitial(HashMap::new);


    /**
     * 使用前置通知,对带有Idempotent注解的进行幂等处理
     */
    @Before("@annotation(idempotent)")
    public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
        // 获取解析器
        KeyResolver keyResolver = SpringUtil.getBean(idempotent.keyResolver());
        Assert.notNull(keyResolver, "找不到对应的幂等解析器");

        // 解析 key
        String key = keyResolver.resolver(joinPoint, idempotent);

        // 锁定 key
        boolean success = idempotentRedisService.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());

        // 锁定失败, 抛出异常
        if (!success) {
            log.info("方法:{} 参数:{} 重复请求!",
                    joinPoint.getSignature().toString(),
                    joinPoint.getArgs());
            throw new RuntimeException(idempotent.message());
        }
        // 缓存注解信息
        Map<String, Object> map = THREAD_LOCAL.get();
        map.put(IdempotentConstant.KEY_PREFIX, key);
        map.put(IdempotentConstant.DEL_KEY_PREFIX, idempotent.delKey());
    }

    /**
     * 使用后置通知,对业务执行完成，需要删除的key进行删除
     */
    @After("@annotation(idempotent)")
    public void afterPointCut(JoinPoint joinPoint, Idempotent idempotent) {
        // 获取缓存的幂等注解信息
        Map<String, Object> map = THREAD_LOCAL.get();
        if (MapUtil.isEmpty(map)) {
            return;
        }

        String key = (String) map.get(IdempotentConstant.KEY_PREFIX);
        Boolean delKey = (Boolean) map.get(IdempotentConstant.DEL_KEY_PREFIX);
        if (!delKey) {
            return;
        }

        // 删除幂等key
        if (!idempotentRedisService.removeKey(key)) {
            log.info("删除幂等key:{}失败！", key);
        }

        // 删除缓存的幂等注解信息
        THREAD_LOCAL.remove();
    }
}
